/************************************************************************
                                                                     
   Xceed Ultimate ListBox for Silverlight                                                                                                                                            
   Copyright (C) 2010 Xceed Software Inc.    
                                                                     
   This program is provided to you under the terms of the GNU General Public  
   License version 2 as published by the Free Software Foundation. 
        
   This program is distributed in the hope that it will be useful, but
   WITHOUT ANY WARRANTY, without even the implied warranty of 
   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
   General Public License for more details.

   You should have received a copy of the GNU General Public License along 
   with this program, if not, write to the Free Software Foundation, Inc., 
   51 Franklin Street, Fifth Floor, Boston, MA  02110-1301, USA.

   This program can be provided to you by Xceed Software Inc. under a
   proprietary commercial license agreement for use in non-Open Source
   projects. Visit Xceed.com to find the commercial edition of 
   Xceed Ultimate ListBox for Silverlight.                                    
                                                                      
 **********************************************************************/

using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.ComponentModel;
using System.Linq;

namespace Xceed.Silverlight.Data
{
  internal sealed class DataSource : IDataEnumerable<DataPath>
  {
    #region Static Fields

    private const int DefaultBufferSize = 100;
    private const int DefaultCacheSize = 30;
    private const int SystemDefaultItemCount = 1000;
    private static readonly TimeSpan DefaultRequestExecutionDelay = TimeSpan.FromMilliseconds( 25 );

    #endregion Static Fields

    #region Constructor

    internal DataSource( DataSourceProvider dataSourceProvider )
    {
      if( dataSourceProvider == null )
        throw new ArgumentNullException( "dataSourceProvider" );

      this.Initialize( dataSourceProvider );
    }

    #endregion

    #region GroupHeadersFootersConfigurationProvider

    public IGroupHeadersFootersSelector GroupHeadersFootersSelector
    {
      get
      {
        return m_groupHandler.GroupHeadersFootersSelector;
      }

      set
      {
        m_groupHandler.GroupHeadersFootersSelector = value;
      }
    }

    #endregion GroupHeadersFootersConfigurationProvider

    #region GroupStatFunctionsSelector

    public IGroupStatFunctionsSelector GroupStatFunctionsSelector
    {
      get
      {
        return m_groupHandler.GroupStatFunctionsSelector;
      }

      set
      {
        m_groupHandler.GroupStatFunctionsSelector = value;
      }
    }

    #endregion GroupStatFunctionsSelector

    #region DefaultItemCount Property

    public Nullable<long> DefaultItemCount
    {
      get
      {
        return m_userSpecifiedItemCount;
      }

      set
      {
        m_userSpecifiedItemCount = value;
      }
    }

    #endregion DefaultItemCount Property

    #region ElementType Property

    internal Type ElementType
    {
      get
      {
        return m_dataSource.ElementType;
      }
    }

    #endregion

    #region DataHelper Property

    internal IDataHelper<DataPath> DataHelper
    {
      get
      {
        return m_dataSourceHelper;
      }
    }

    #endregion

    #region GroupHelper Property

    internal IGroupDataHelper GroupHelper
    {
      get
      {
        return m_groupHelper;
      }
    }

    #endregion

    #region ProviderHelper

    internal IDataHelper ProviderHelper
    {
      get
      {
        return m_providerHelper;
      }
    }

    #endregion

    #region Filter Property

    internal Predicate<object> Filter
    {
      get
      {
        return m_filteringHandler.Filter;
      }
      set
      {
        m_filteringHandler.Filter = value;
      }
    }

    #endregion Filter Property

    #region FilterExpression Property

    internal FilterExpressionBase FilterExpression
    {
      get
      {
        return m_filteringHandler.FilterExpression;
      }
      set
      {
        m_filteringHandler.FilterExpression = value;
      }
    }

    #endregion FilterExpression Property

    #region SortDescriptions Property

    internal SortDescriptionCollection SortDescriptions
    {
      get
      {
        return m_sortingHandler.SortDescriptions;
      }
    }

    #endregion SortDescriptions Property

    #region GroupDescriptions Property

    internal ObservableCollection<GroupDescription> GroupDescriptions
    {
      get
      {
        return m_groupHandler.GroupDescriptions;
      }
    }

    #endregion GroupDescriptions Property

    #region Provider Property

    internal DataSourceProvider Provider
    {
      get
      {
        return m_dataSource;
      }
    }

    #endregion

    #region PrimaryKeyInfo Property

    internal IEnumerable<SortDescription> PrimaryKeyInfo
    {
      get
      {
        return m_dataSource.PrimaryKeyInfo;
      }
    }

    #endregion

    #region RequestExecutionDelay Property

    internal TimeSpan RequestExecutionDelay
    {
      get
      {
        return m_throttlingHandler.RequestExecutionDelay;
      }
      set
      {
        m_throttlingHandler.RequestExecutionDelay = value;
      }
    }

    #endregion RequestExecutionDelay Property

    #region CollapsedGroupCollections Property

    internal ReadOnlyCollection<GroupCollection> CollapsedGroupCollections
    {
      get
      {
        return m_groupHandler.CollapsedGroupCollections;
      }
    }

    #endregion CollapsedGroupCollections Property

    #region AppliedSortDescriptions Property

    internal SortDescription[] AppliedSortDescriptions
    {
      get
      {
        return this.DataHelper.CompleteSort;
      }
    }

    #endregion AppliedSortDescriptions Property

    #region IsPrimaryKeyVolatile Property

    internal bool IsPrimaryKeyVolatile
    {
      get
      {
        return m_dataSource.IsPrimaryKeyVolatile;
      }
    }

    #endregion IsPrimaryKeyVolatile Property

    #region CurrentItem Property

    internal object CurrentItem
    {
      get
      {
        return m_currentItem;
      }
    }

    #endregion CurrentItem Property

    #region SourceChanged Event

    internal event EventHandler<SourceChangedEventArgs> SourceChanged;

    private void OnSourceChanged( SourceChangedEventArgs e )
    {
      this.DisposeEnumerators();

      if( this.SourceChanged != null )
      {
        this.SourceChanged( this, e );
      }
    }

    #endregion SourceChanged Event

    #region ExpansionChanged Event

    internal event EventHandler<ExpansionChangedEventArgs> ExpansionChanged;

    private void OnExpansionChanged( ExpansionChangedEventArgs args )
    {
      if( this.ExpansionChanged != null )
      {
        this.ExpansionChanged( this, args );
      }
    }

    #endregion ExpansionChanged Event

    private void RegisterDisposableEnumerator( IDisposable enumerator )
    {
      if( enumerator != null )
      {
        m_activeEnumerators.Add( new WeakReference( enumerator ) );
      }
    }

    private void Initialize( DataSourceProvider dataSourceProvider )
    {
      m_dataSource = dataSourceProvider;
      m_dataSource.Init();
      m_dataSource.CheckMinimumRequirements();

      m_filteringHandler = new FilteringHandler();
      m_groupHandler = new GroupHandler( this );
      m_sortingHandler = new SortingHandler();
      m_throttlingHandler = new ThrottlingHandler();

      m_throttlingHandler.RequestExecutionDelay = DataSource.DefaultRequestExecutionDelay;
      m_providerHelper = new DataSourceProviderHelper( m_dataSource, m_groupHandler, m_sortingHandler );
      m_dataPathHelper = new DataPathHelper( m_providerHelper );
      m_groupHelper = new GroupHelper( m_dataPathHelper, m_groupHandler );
      m_reactiveHelper = new ReactiveHelper( m_groupHelper );
      m_dataSourceHelper = new DataSourceHelper( m_reactiveHelper );

      m_filteringHandler.SourceChanged += new EventHandler<SourceChangedEventArgs>( InternalSourceChanged );
      m_sortingHandler.SourceChanged += new EventHandler<SourceChangedEventArgs>( InternalSourceChanged );
      m_groupHandler.SourceChanged += new EventHandler<SourceChangedEventArgs>( InternalSourceChanged );
      m_groupHandler.ExpansionChanged += new EventHandler<ExpansionChangedEventArgs>( GroupCollapseChanged );

      if( dataSourceProvider.CanCount  )
      {
        if( !dataSourceProvider.IsAsynchronous )
        {
          m_groupHandler.GroupCountPolicy = GroupCountPolicy.Query;
          m_groupHandler.IncludeGroupItems = true;
          m_groupHandler.IncludeGroupItemsCount = true;
        }
        else
        {
          m_groupHandler.GroupCountPolicy = GroupCountPolicy.CacheAsEstimates;
        }
      }
    }

    private static IAsyncEnumerator<DataPath> GetStandAloneDataPathEnumerator(
      DataSourceProvider dataSource,
      FilteringHandler filteringHandler,
      DataSourceProviderHelper dataSourceProviderHelper,
      GroupHelper groupHelper )
    {
      if( dataSource == null )
        throw new ArgumentNullException( "dataSource" );

      if( dataSourceProviderHelper == null )
        throw new ArgumentNullException( "dataSourceProviderHelper" );

      if( groupHelper == null )
        throw new ArgumentNullException( "groupHelper" );

      // Make sure the DataSourceProvider supports the minimum that is required to
      // navigate.
      DataSource.CheckCapabilities( dataSource, dataSourceProviderHelper );

      // This enumerator converts a query from the JumpMoveAndFetch API to a query of
      // the QueryRequest API.
      IAsyncEnumerator<RawDataItem> objectEnumerator = new DataSourceProviderEnumerator<RawDataItem>(
                                                         dataSource,
                                                         dataSourceProviderHelper,
                                                         filteringHandler );

      // Set a few enumerators that will convert the queries based on the capabilities
      // of the DataSourceProvider.
      objectEnumerator = DataSource.GetNavigationEnumerator(
                           objectEnumerator,
                           dataSource,
                           dataSourceProviderHelper,
                           dataSource.CanSkip );

      // Set a paging enumerator that will slice one big fetch into many smaller fetches if
      // paging is enabled.
      Func<int> getPageSize = () => dataSource.GetPageSize();
      if( dataSource.CanSkip )
      {
        // This enumerator uses a skip operation to jump from one page to another.
        objectEnumerator = new SkipPagingEnumerator<RawDataItem>( objectEnumerator, getPageSize );
      }
      else
      {
        // This enumerator emulate a skip operation necessary to jump from one page to another
        // with a take operation.
        Func<IEnumerable<SortDescription>> getCurrentSort = () => dataSourceProviderHelper.CompleteSort;
        objectEnumerator = new TakePagingEnumerator<RawDataItem>( objectEnumerator, getPageSize, getCurrentSort );
      }


      bool isAsynchronousDataSource = dataSource.IsAsynchronous;
      bool isCachingEnabled = ( isAsynchronousDataSource || dataSource.IsCachingAllowed );
      bool isBufferingEnabled = ( isCachingEnabled && dataSource.IsBufferingAllowed );
      IBufferConfigurable bufferManager = dataSource as IBufferConfigurable;

      // This enumerator requests additional items to minimize the number of queries done
      // on a slow data source.
      if( isBufferingEnabled && bufferManager != null )
      {
        objectEnumerator = new BufferingEnumerator<RawDataItem>(
                                 objectEnumerator,
                                 dataSourceProviderHelper,
                                 bufferManager );
      }

      // Add an enumerator that will filter the data objects locally if it is necessary.
      objectEnumerator = DataSource.GetFilteringEnumerator(
                           objectEnumerator,
                           dataSource,
                           filteringHandler );

      // Add an enumerator that will find out the number of items in the data source
      // and add it to every data query result.
      if( dataSource.CanCount )
      {
        objectEnumerator = new TotalCountEnumerator<RawDataItem>( objectEnumerator );
      }

      // This enumerator wraps each data object into a DataPath.
      IAsyncEnumerator<DataPath> dataPathEnumerator = new DataPathEnumerator( objectEnumerator );

      // This enumerator handles any grouping functionality.
      return new GroupEnumerator( dataPathEnumerator, groupHelper );
    }

    private static IAsyncEnumerator<RawDataItem> GetNavigationEnumerator(
      IAsyncEnumerator<RawDataItem> childEnumerator,
      DataSourceProvider dataSource,
      IDataHelper dataHelper,
      bool requiresOneWayNavigation )
    {
      IAsyncEnumerator<RawDataItem> objectEnumerator = childEnumerator;

      bool canJump = dataSource.CanJump;
      bool canJumpToItem = dataSource.CanJumpToItem;
      bool canJumpToDeletedItem = dataSource.CanJumpToDeletedItem;
      bool isNavigationFilterSupported = !( dataSource.IsPrimaryKeyVolatile )
                                       && ( NavigationFilterUtils.IsSupported( dataSource, dataHelper.CompleteSort ) );
      bool addOneWayEnumerator = requiresOneWayNavigation;
      bool loop;

      do
      {
        loop = false;

        // We must set an enumerator that is looking for a SortPosition even if jumping or
        // filtering isn't supported since the [Deleted|Item]PositionConverterEnumerator may 
        // generate queries that use SortPosition.
        if( ( !canJump ) || ( !isNavigationFilterSupported ) )
        {
          objectEnumerator = new SortPositionFinderEnumerator<RawDataItem>( objectEnumerator, dataSource, dataHelper );
        }

        // Set an enumerator that is converting unusable ItemPosition into SortPosition.
        if( ( canJumpToItem ) && ( !canJumpToDeletedItem ) )
        {
          objectEnumerator = new DeletedItemPositionConverterEnumerator<RawDataItem>( objectEnumerator, dataHelper );
        }
        // Set an enumerator that is converting every ItemPosition into SortPosition.
        else if( !canJumpToItem )
        {
          objectEnumerator = new ItemPositionConverterEnumerator<RawDataItem>( objectEnumerator, dataHelper );
        }

        if( addOneWayEnumerator )
        {
          // This enumerator transform any bidirectional query into unidirectional queries.
          objectEnumerator = new OneWayEnumerator<RawDataItem>( objectEnumerator );

          // We need to loop once more to add the converters/finder enumerators prior
          // to the last added enumerator.
          loop = true;
          addOneWayEnumerator = false;
        }
      } while( loop );

      return objectEnumerator;
    }

    private static IAsyncEnumerator<RawDataItem> GetFilteringEnumerator(
      IAsyncEnumerator<RawDataItem> childEnumerator,
      DataSourceProvider dataSource,
      FilteringHandler filteringHandler )
    {
      // There is no need to add the filtering enumerator.
      if( filteringHandler == null )
        return childEnumerator;

      // Check out if the data source supports the current filter predicate.
      Predicate<RawDataItem> filterPredicate = null;
      if( !dataSource.CanFilterBy( filteringHandler.Filter ) )
      {
        Predicate<object> predicate = filteringHandler.Filter;
        filterPredicate = ( dataItem ) => predicate.Invoke( dataItem.Data );
      }

      // Check out if the data source supports the current filter expression.
      Predicate<RawDataItem> filterExpressionPredicate = filteringHandler.FilterDataItem;
      if( !dataSource.CanFilterBy( filteringHandler.FilterExpression ) )
      {
        PredicateBuilder pb = new PredicateBuilder( filteringHandler.FilterExpression );
        Func<object, bool> predicate = pb.ToPredicate<object>(
                                         dataSource.ElementType,
                                         dataSource.GetElementMemberType );

        if( predicate != null )
        {
          Predicate<RawDataItem> filterExpression = ( dataItem ) => predicate.Invoke( dataItem );

          if( filterExpressionPredicate != null )
          {
            Predicate<RawDataItem> currentFilterExpressionPredicate = filterExpressionPredicate;
            filterExpressionPredicate = ( dataItem ) => currentFilterExpressionPredicate.Invoke( dataItem ) && filterExpression.Invoke( dataItem );
          }
          else
          {
            filterExpressionPredicate = filterExpression;
          }
        }
      }

      // Combine the two filter predicates.
      if( filterPredicate != null && filterExpressionPredicate != null )
      {
        Predicate<RawDataItem> predicate = ( RawDataItem dataItem ) => filterPredicate.Invoke( dataItem )
                                                                    && filterExpressionPredicate.Invoke( dataItem );
        return new FilterEnumerator<RawDataItem>( childEnumerator, predicate );
      }
      else if( filterPredicate != null )
      {
        return new FilterEnumerator<RawDataItem>( childEnumerator, filterPredicate );
      }
      else if( filterExpressionPredicate != null )
      {
        return new FilterEnumerator<RawDataItem>( childEnumerator, filterExpressionPredicate );
      }
      // There is no need to add the filtering enumerator.
      else
      {
        return childEnumerator;
      }
    }

    private static void CheckCapabilities(
      DataSourceProvider dataSource,
      DataSourceProviderHelper dataHelper )
    {
      bool canSkip = dataSource.CanSkip;
      bool canJump = dataSource.CanJump;
      bool canJumpToItem = dataSource.CanJumpToItem;
      bool canFilter = NavigationFilterUtils.IsSupported( dataSource, dataHelper.CompleteSort );
      bool isPrimaryKeyVolatile = dataSource.IsPrimaryKeyVolatile;

      // The DataSourceProvider doesn't allow navigation.
      if( !canSkip && !canJumpToItem && !canJump && !canFilter )
      {
        throw new InvalidOperationException( "An attempt was made to use a DataSourceProvider that does not provide navigation capabilities." );
      }
      // The DataSourceProvider allows item jumping which means ItemPosition are supported.
      // However, there is no way for us to find out the neighbors of an item that has been
      // removed from the data source after we've received a notification.
      else if( !canSkip && !canFilter )
      {
        throw new InvalidOperationException( "An attempt was made to use a DataSourceProvider that does not allow skipping and/or filtering." );
      }
      // The primary key used by the DataSourceProvider is volatile, which means it is
      // sensitive to the query's context.  For this kind of DataSourceProvider, jumping
      // is unreliable or unsupported while navigating with filters is forbidden due to
      // the volatile state of the primary key.  In other words, skipping is the only
      // usable way to navigate.
      else if( !canSkip && isPrimaryKeyVolatile )
      {
        throw new InvalidOperationException( "An attempt was made to use a DataSourceProvider that does not allow skipping." );
      }
    }

    private DataSourceEnumeratorBase GetDataSourceEnumerator( bool dummyItemsAllowed )
    {
      IAsyncEnumerator<DataPath> dataPathEnumerator = DataSource.GetStandAloneDataPathEnumerator(
                                                        m_dataSource,
                                                        m_filteringHandler,
                                                        m_providerHelper,
                                                        m_groupHelper );
      bool isAsynchronousDataSource = m_dataSource.IsAsynchronous;
      bool isCachingEnabled = isAsynchronousDataSource || m_dataSource.IsCachingAllowed;
      DataSourceEnumeratorBase enumerator;

      if( isAsynchronousDataSource && dummyItemsAllowed )
      {
        GroupEnumerator groupEnumerator = ( GroupEnumerator )dataPathEnumerator;

        // This enumerator creates and caches dummy items for each pending data objects.
        ReactiveDataPathEnumerator reactiveEnumerator = new ReactiveDataPathEnumerator(
          groupEnumerator,
          m_reactiveHelper.DataItemFactory,
          m_throttlingHandler,
          m_groupHelper );

        ReactiveCacheEnumerator cacheEnumerator = new ReactiveCacheEnumerator( DataSource.DefaultCacheSize, m_reactiveHelper, reactiveEnumerator );

        // Cache lookup for GroupEnumerator.
        groupEnumerator.CachedPathProvider = cacheEnumerator.ProvideCachedPath;

        // This enumerator handles the item's position and manage inconsistencies
        // from the dummy items.
        enumerator = new StandardDataSourceEnumerator( cacheEnumerator, reactiveEnumerator, m_dataSourceHelper, m_groupHelper );
      }
      else
      {
        // This enumerator caches items to retrieve them quickly.
        if( isCachingEnabled )
        {
          dataPathEnumerator = new CacheEnumerator<DataPath>(
                                     DataSource.DefaultCacheSize,
                                     m_dataSourceHelper,
                                     dataPathEnumerator );
        }

        // This enumerator handles the item's position.
        enumerator = new LocalDataSourceEnumerator( dataPathEnumerator, m_dataSourceHelper, m_groupHelper );

        // Behave like a "real" local list. Do not allow "approximate" moves.
        enumerator.EnableApproximateMoves = false;
      }

      // Set a default item count for a potential scroll bar.
      Nullable<long> initialItemCount = null;

      if( m_dataSource is ICountRestrictedProvider )
      {
        initialItemCount = ( ( ICountRestrictedProvider )m_dataSource ).InitialItemCount;
      }

      if( initialItemCount != null )
      {
        enumerator.SetCountValues( null, new EstimateItemCount( initialItemCount.Value, true, false ) );
      }
      else
      {
        enumerator.SetCountValues( null, new EstimateItemCount( DataSource.SystemDefaultItemCount, true, true ) );
      }

      return enumerator;
    }

    private void InternalSourceChanged( object sender, SourceChangedEventArgs args )
    {
      this.OnSourceChanged( args );
    }

    private void GroupCollapseChanged( object sender, ExpansionChangedEventArgs args )
    {
      this.OnExpansionChanged( args );
    }

    private void DisposeEnumerators()
    {
      foreach( WeakReference enumRef in m_activeEnumerators )
      {
        IDisposable enumerator = enumRef.Target as IDisposable;
        if( enumerator != null )
        {
          enumerator.Dispose();
        }
      }

      m_activeEnumerators.Clear();
    }

    internal bool MoveCurrentTo( DataPath item )
    {
      m_currentItem = item;
      return false;
    }

    internal object GetDataObjectPropertyValue( string propertyName, object item )
    {
      return m_providerHelper.GetMemberValue( propertyName, item );
    }

    internal ISyncDataSourceEnumerator GetSyncDataEnumerator()
    {
      DataSourceEnumeratorBase asyncEnumerator = this.GetDataSourceEnumerator( true );

      asyncEnumerator.PreventEstimateIndex = true;
      asyncEnumerator.ProviderSupportGroupCount = m_dataSource.CanGroupCount;

      ISyncDataSourceEnumerator enumerator = this.GetSyncDataEnumerator( asyncEnumerator );


      this.RegisterDisposableEnumerator( enumerator );

      return enumerator;
    }

    internal ISyncDataSourceEnumerator GetSyncDataEnumerator( DataSourceEnumeratorBase asyncEnumerator )
    {
      if( asyncEnumerator is ICachingEnumerator )
      {
        return new SyncCachingDataSourceEnumerator( asyncEnumerator );
      }
      else
      {
        return new SyncDataSourceEnumerator( asyncEnumerator );
      }
    }

    internal ISyncDataSourceEnumerator GetSyncDebugLogDataSourceEnumerator( ISyncDataSourceEnumerator enumerator )
    {
      if( enumerator is ICachingEnumerator )
      {
        return new SyncCachingDebugLogDataSourceEnumerator( enumerator );
      }
      else
      {
        return new SyncDebugLogDataSourceEnumerator( enumerator );
      }
    }

    internal IAsyncDataSourceEnumerator GetAsyncDataEnumerator()
    {
      DataSourceEnumeratorBase enumerator = this.GetDataSourceEnumerator( false );

      this.RegisterDisposableEnumerator( enumerator );

      return enumerator;
    }

    internal DataRangeContext GetCurrentDataRangeContext()
    {
      return new DataRangeContext(
                   m_dataSource.ElementType,
                   this.AppliedSortDescriptions,
                   this.Filter,
                   this.FilterExpression );
    }

    internal IDataRangeEnumerator GetDataRangeEnumerator( IEnumerable<DataRange> ranges )
    {
      return this.GetDataRangeEnumerator(
        ranges,
        Enumerable.Empty<GroupDescription>(),
        Enumerable.Empty<SortDescription>() );
    }

    internal IDataRangeEnumerator GetDataRangeEnumerator( 
      IEnumerable<DataRange> ranges,
      IEnumerable<GroupDescription> groupDescriptions,
      IEnumerable<SortDescription> sortDescriptions )
    {
      ranges = OptimizeDataRanges( ranges );

      // If no range is specified, then no data will be returned.
      if( ranges == null )
      {
        ranges = Enumerable.Empty<DataRange>();
      }

      Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> enumeratorCreator = DataSource.GetDataPathEnumeratorCreator();
      Func<GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> dataPathEnumeratorCreator =
          ( groupHandler, sortHandler, filterHandler ) =>
            enumeratorCreator.Invoke(
              m_dataSource,
              groupHandler,
              sortHandler,
              filterHandler );

      // Find out if the data source has a query length limitation.
      bool isQueryLengthLimited = m_dataSource.IsQueryLengthLimited;

      // Find out if the data source handles filter expressions that are
      // use for navigation purpose.
      IEnumerable<string> sortedPropertiesNames =
        groupDescriptions.Select( ( gd ) => gd.GetPropertyName() )
                         .Concat( sortDescriptions.Select( ( sd ) => sd.PropertyName ) )
                         .Concat( m_dataSource.PrimaryKeyInfo.Select( ( sd ) => sd.PropertyName ) );
      bool isNavigationFilterSupported = NavigationFilterUtils.IsSupported( m_dataSource, sortedPropertiesNames );

      // To use a GroupDataRangeEnumerator, the underlying data source must
      // be able to apply filter expressions that will bound the desired data.
      // Since the filter expressions can be huge, the data source must not
      // have an query length limitation.
      bool getAllDataItemsBeforeGrouping = ( isQueryLengthLimited )
                                        || ( !isNavigationFilterSupported );

      // We will need to download all the items that are within the range before grouping
      // and sorting them locally.
      IDataRangeEnumerator dataRangeEnumerator;
      if( getAllDataItemsBeforeGrouping )
      {
        dataRangeEnumerator = new LocalGroupDataRangeEnumerator(
                                ranges,
                                groupDescriptions,
                                sortDescriptions,
                                m_dataSource.PrimaryKeyInfo,
                                m_dataSource.ElementType,
                                dataPathEnumeratorCreator,
                                enumeratorCreator );
      }
      // The remote data source can handle the grouping and the sorting for the ranges.
      else
      {
        dataRangeEnumerator = new GroupDataRangeEnumerator(
                                ranges,
                                groupDescriptions,
                                sortDescriptions,
                                m_dataSource.PrimaryKeyInfo,
                                m_dataSource.ElementType,
                                m_dataSource.GetElementMemberType,
                                dataPathEnumeratorCreator );
      }

      return dataRangeEnumerator;
    }

    private IEnumerable<DataRange> OptimizeDataRanges( IEnumerable<DataRange> ranges )
    {
      if( ranges == null || (ranges.Count() == 0) )
      {
        return null;
      }

      List<DataRange> dataRanges = ranges.ToList();
      List<DataRange> RangesToRemove = new List<DataRange>();


      Nullable<int> lastUnSelectionRangeID = null;
      for( int i = dataRanges.Count - 1; i >= 0; --i )
      {
        if( !dataRanges[ i ].IsSelectionRangeInclusive )
        {
          lastUnSelectionRangeID = i;
          break;
        }
      }

      if( ( lastUnSelectionRangeID != null ) && ( lastUnSelectionRangeID != ( dataRanges.Count - 1 ) ) )
      {
        for( int i = ( int )lastUnSelectionRangeID + 1; i < dataRanges.Count; ++i )
        {
          for( int j = 0; j < ( int )lastUnSelectionRangeID; ++j )
          {
            if( dataRanges[ j ].IsSameRange( dataRanges[ i ] ) )
            {
              RangesToRemove.Add( dataRanges[ j ] );
            }
          }
        }
      }
      RemoveFromDataRanges( RangesToRemove, dataRanges );


      RangesToRemove.Clear();
      for( int i = dataRanges.Count - 1; i >= 0; --i )
      {
        if( !dataRanges[ i ].IsSelectionRangeInclusive )
        {
          for( int j = i - 1; j >= 0; --j )
          {
            if( dataRanges[ j ].IsSameRange( dataRanges[ i ] )
              && !dataRanges[ j ].IsSelectionRangeInclusive )
            {
              RangesToRemove.Add( dataRanges[ j ] );
              break;
            }
          }
        }
      }
      RemoveFromDataRanges( RangesToRemove, dataRanges );


      RangesToRemove.Clear();
      int id = 0;
      while( (id < dataRanges.Count()) &&  !dataRanges[ id ].IsSelectionRangeInclusive )
      {
        RangesToRemove.Add( dataRanges[ id ] );
        ++id;
      }
      RemoveFromDataRanges( RangesToRemove, dataRanges );


      RangesToRemove.Clear();
      if( dataRanges.Count > 1 )
      {
        for( int i = 0; i < dataRanges.Count - 1; ++i )
        {
          if( ( dataRanges[ i ].IsSameRange( dataRanges[ i + 1 ] ) )
            && ( dataRanges[ i ].IsSelectionRangeInclusive == dataRanges[ i + 1 ].IsSelectionRangeInclusive ) )
          {
            RangesToRemove.Add( dataRanges[ i ] );
          }
        }
        RemoveFromDataRanges( RangesToRemove, dataRanges );
      }

      return ( IEnumerable<DataRange> )dataRanges;
    }

    private void RemoveFromDataRanges( List<DataRange> RangesToRemove, List<DataRange> dataRanges )
    {
      for( int i = 0; i < RangesToRemove.Count; ++i )
      {
        dataRanges.Remove( RangesToRemove[ i ] );
      }
    }

    internal void InvalidateData()
    {
      this.DisposeEnumerators();

      if( m_dataSource != null )
      {
        m_dataSource.InvalidateData();
      }
    }

    internal void InvalidateDataPath( DataPath dataPath )
    {
      if( m_dataSourceHelper != null )
      {
        m_dataSourceHelper.InvalidateItem( dataPath );
      }
    }

    internal IEnumerable<FilterOperator> GetSupportedFilterOperators( Type dataType )
    {
      return m_dataSource.SupportedFilterOperators( dataType );
    }

    private static Func<DataSourceProvider, GroupHandler, SortingHandler, FilteringHandler, IAsyncEnumerator<DataPath>> GetDataPathEnumeratorCreator()
    {
      return ( dataSource, groupHandler, sortHandler, filterHandler ) =>
        {
          DataSourceProviderHelper dataSourceProviderHelper = new DataSourceProviderHelper( dataSource, groupHandler, sortHandler );
          DataPathHelper dataPathHelper = new DataPathHelper( dataSourceProviderHelper );
          GroupHelper groupHelper = new GroupHelper( dataPathHelper, groupHandler );

          return DataSource.GetStandAloneDataPathEnumerator(
                   dataSource,
                   filterHandler,
                   dataSourceProviderHelper,
                   groupHelper );
        };
    }

    #region IDataEnumerable<DataPath> Members

    IAsyncEnumerator<DataPath> IDataEnumerable<DataPath>.GetAsyncDataEnumerator()
    {
      return this.GetAsyncDataEnumerator();
    }

    #endregion IDataEnumerable<DataPath> Members

    #region Private Fields

    private DataSourceProvider m_dataSource;
    private DataPath m_currentItem; // null
    private FilteringHandler m_filteringHandler;
    private List<WeakReference> m_activeEnumerators = new List<WeakReference>();

    private DataSourceProviderHelper m_providerHelper;
    private DataPathHelper m_dataPathHelper;
    private GroupHelper m_groupHelper;
    private ReactiveHelper m_reactiveHelper;
    private DataSourceHelper m_dataSourceHelper;
    private SortingHandler m_sortingHandler;
    private GroupHandler m_groupHandler;
    private ThrottlingHandler m_throttlingHandler;

    private Nullable<long> m_userSpecifiedItemCount; //null

    #endregion
  }
}
